#include <stdio.h>
#include <string.h>
#include <stdarg.h>
#include <time.h>
#include "zvty.h"


#define NUL     0
#define BEL     7
#define BS      8
#define HT      9
#define LF     10
#define VT     11
#define FF     12
#define CR     13
#define SE    240
#define NOP   241
#define DM    242
#define BRK   243
#define IP    244
#define AO    245
#define AYT   246
#define EC    247
#define EL    248
#define GA    249
#define SB    250
#define WILL  251
#define WONT  252
#define DO    253
#define DONT  254
#define IAC   255


#define TELOPT_ECHO   1
#define TELOPT_SGA    3
#define TELOPT_TTYPE 24
#define TELOPT_NAWS  31
#define TELOPT_LINEMODE 35

#define CONTROL(X)  ((X) - '@')
#define VTY_NORMAL_ESC 0
#define VTY_PRE_ESCAPE 1
#define VTY_ESCAPE     2
static const char telnet_backward_char = 0x08;
static const char telnet_space_char = ' ';

int
vty_shell (ZVTY *vty)
{
	return vty->type == ZVTY::VTY_SHELL ? 1 : 0;
}

/* Basic function to write buffer to vty. */
static void
vty_write (ZVTY *vty, const char *buf, size_t nbytes)
{
	if ((vty->node == ZVTY::AUTH_NODE))
		return;

	/* Should we do buffering here ?  And make vty_flush (vty) ? */
	//buffer_put (vty->obuf, buf, nbytes);
	vty->m_stub->vty_write(buf,nbytes);
}
/* VTY standard output function. */
int
vty_out (ZVTY *vty, const char *format, ...)
{
	va_list args;
	int len = 0;
	int size = 1024;
	char buf[1024];
	char *p = NULL;

	if (vty_shell (vty))
	{
		va_start (args, format);
		vprintf (format, args);
		va_end (args);
	}
	else
	{
		/* Try to write to initial buffer.  */
		va_start (args, format);
		len = vsnprintf (buf, sizeof buf, format, args);
		va_end (args);

		/* Initial buffer is not enough.  */
		if (len < 0 || len >= size)
		{
			while (1)
			{
				if (len > -1)
					size = len + 1;
				else
					size = size * 2;

				p = (char*)realloc (p, size);
				if (! p)
					return -1;

				va_start (args, format);
				len = vsnprintf (p, size, format, args);
				va_end (args);

				if (len > -1 && len < size)
					break;
			}
		}

		/* When initial buffer is enough to store all output.  */
		if (! p)
			p = buf;

		/* Pointer p must point out buffer. */
		vty->m_stub->vty_write(p, len);

		/* If p is not different with buf, it is allocated buffer.  */
		if (p != buf)
			free(p);
	}

	return len;
}
/* Say hello to vty interface. */
void
vty_hello (ZVTY *vty)
{
	vty->m_stub->vty_hello();
}
/* Put out prompt and wait input from user. */
static void
vty_prompt (ZVTY *vty)
{
	switch(vty->node)
	{
	case ZVTY::NAME_NODE:
		vty_out(vty,"User: ");
		break;
	case ZVTY::AUTH_NODE:
		vty_out(vty,"Password: ");
		break;
	default:
		vty->m_stub->vty_prompt();
		break;
	}
}

/* Send WILL TELOPT_ECHO to remote server. */
static void
vty_will_echo (ZVTY *vty)
{
	unsigned char cmd[] = { IAC, WILL, TELOPT_ECHO, '\0' };
	vty_out (vty, "%s", cmd);
}

/* Make suppress Go-Ahead telnet option. */
static void
vty_will_suppress_go_ahead (ZVTY *vty)
{
	unsigned char cmd[] = { IAC, WILL, TELOPT_SGA, '\0' };
	vty_out (vty, "%s", cmd);
}

/* Make don't use linemode over telnet. */
static void
vty_dont_linemode (ZVTY *vty)
{
	unsigned char cmd[] = { IAC, DONT, TELOPT_LINEMODE, '\0' };
	vty_out (vty, "%s", cmd);
}

/* Use window size. */
static void
vty_do_window_size (ZVTY *vty)
{
	unsigned char cmd[] = { IAC, DO, TELOPT_NAWS, '\0' };
	vty_out (vty, "%s", cmd);
}

/* Ensure length of input buffer.  Is buffer is short, double it. */
static void
vty_ensure (ZVTY *vty, int length)
{
	if (vty->max <= length)
	{
		vty->max *= 2;
		vty->buf = (char*)realloc(vty->buf, vty->max);
	}
}

/* Basic function to insert character into vty. */
static void
vty_self_insert (ZVTY *vty, char c)
{
	int i;
	int length;

	vty_ensure (vty, vty->length + 1);
	length = vty->length - vty->cp;
	memmove (&vty->buf[vty->cp + 1], &vty->buf[vty->cp], length);
	vty->buf[vty->cp] = c;

	vty_write (vty, &vty->buf[vty->cp], length + 1);
	for (i = 0; i < length; i++)
		vty_write (vty, &telnet_backward_char, 1);

	vty->cp++;
	vty->length++;
}

/* Self insert character 'c' in overwrite mode. */
static void
vty_self_insert_overwrite (ZVTY *vty, char c)
{
	vty_ensure (vty, vty->length + 1);
	vty->buf[vty->cp++] = c;

	if (vty->cp > vty->length)
		vty->length++;

	//if ((vty->node == AUTH_NODE) || (vty->node == AUTH_ENABLE_NODE))
	//	return;

	vty_write (vty, &c, 1);
}

/* Insert a word into vty interface with overwrite mode. */
static void
vty_insert_word_overwrite (ZVTY *vty, char *str)
{
	int len = strlen (str);
	vty_write (vty, str, len);
	strcpy (&vty->buf[vty->cp], str);
	vty->cp += len;
	vty->length = vty->cp;
}

/* Forward character. */
static void
vty_forward_char (ZVTY *vty)
{
	if (vty->cp < vty->length)
	{
		vty_write (vty, &vty->buf[vty->cp], 1);
		vty->cp++;
	}
}

/* Backward character. */
static void
vty_backward_char (ZVTY *vty)
{
	if (vty->cp > 0)
	{
		vty->cp--;
		vty_write (vty, &telnet_backward_char, 1);
	}
}

/* Move to the beginning of the line. */
static void
vty_beginning_of_line (ZVTY *vty)
{
	while (vty->cp)
		vty_backward_char (vty);
}

/* Move to the end of the line. */
static void
vty_end_of_line (ZVTY *vty)
{
	while (vty->cp < vty->length)
		vty_forward_char (vty);
}

static void vty_kill_line_from_beginning (ZVTY *);
static void vty_redraw_line (ZVTY *);

/* Print command line history.  This function is called from
vty_next_line and vty_previous_line. */
static void
vty_history_print (ZVTY *vty)
{
	int length;

	vty_kill_line_from_beginning (vty);

	/* Get previous line from history buffer */
	length = strlen (vty->hist[vty->hp]);
	memcpy (vty->buf, vty->hist[vty->hp], length);
	vty->cp = vty->length = length;

	/* Redraw current line */
	vty_redraw_line (vty);
}

/* Show next command line history. */
static void
vty_next_line (ZVTY *vty)
{
	int try_index;

	if (vty->hp == vty->hindex)
		return;

	/* Try is there history exist or not. */
	try_index = vty->hp;
	if (try_index == (VTY_MAXHIST - 1))
		try_index = 0;
	else
		try_index++;

	/* If there is not history return. */
	if (vty->hist[try_index] == NULL)
		return;
	else
		vty->hp = try_index;

	vty_history_print (vty);
}

/* Show previous command line history. */
static void
vty_previous_line (ZVTY *vty)
{
	int try_index;

	try_index = vty->hp;
	if (try_index == 0)
		try_index = VTY_MAXHIST - 1;
	else
		try_index--;

	if (vty->hist[try_index] == NULL)
		return;
	else
		vty->hp = try_index;

	vty_history_print (vty);
}

/* This function redraw all of the command line character. */
static void
vty_redraw_line (ZVTY *vty)
{
	vty_write (vty, vty->buf, vty->length);
	vty->cp = vty->length;
}

/* Forward word. */
static void
vty_forward_word (ZVTY *vty)
{
	while (vty->cp != vty->length && vty->buf[vty->cp] != ' ')
		vty_forward_char (vty);

	while (vty->cp != vty->length && vty->buf[vty->cp] == ' ')
		vty_forward_char (vty);
}

/* Backward word without skipping training space. */
static void
vty_backward_pure_word (ZVTY *vty)
{
	while (vty->cp > 0 && vty->buf[vty->cp - 1] != ' ')
		vty_backward_char (vty);
}

/* Backward word. */
static void
vty_backward_word (ZVTY *vty)
{
	while (vty->cp > 0 && vty->buf[vty->cp - 1] == ' ')
		vty_backward_char (vty);

	while (vty->cp > 0 && vty->buf[vty->cp - 1] != ' ')
		vty_backward_char (vty);
}

/* Delete a charcter at the current point. */
static void
vty_delete_char (ZVTY *vty)
{
	int i;
	int size;

	if (vty->length == 0)
	{
		//vty_down_level (vty);
		return;
	}

	if (vty->cp == vty->length)
		return;			/* completion need here? */

	size = vty->length - vty->cp;

	vty->length--;
	memmove (&vty->buf[vty->cp], &vty->buf[vty->cp + 1], size - 1);
	vty->buf[vty->length] = '\0';

	//if (vty->node == AUTH_NODE || vty->node == AUTH_ENABLE_NODE)
	//	return;

	vty_write (vty, &vty->buf[vty->cp], size - 1);
	vty_write (vty, &telnet_space_char, 1);

	for (i = 0; i < size; i++)
		vty_write (vty, &telnet_backward_char, 1);
}

/* Delete a character before the point. */
static void
vty_delete_backward_char (ZVTY *vty)
{
	if (vty->cp == 0)
		return;

	vty_backward_char (vty);
	vty_delete_char (vty);
}

/* Kill rest of line from current point. */
static void
vty_kill_line (ZVTY *vty)
{
	int i;
	int size;

	size = vty->length - vty->cp;

	if (size == 0)
		return;

	for (i = 0; i < size; i++)
		vty_write (vty, &telnet_space_char, 1);
	for (i = 0; i < size; i++)
		vty_write (vty, &telnet_backward_char, 1);

	memset (&vty->buf[vty->cp], 0, size);
	vty->length = vty->cp;
}

/* Kill line from the beginning. */
static void
vty_kill_line_from_beginning (ZVTY *vty)
{
	vty_beginning_of_line (vty);
	vty_kill_line (vty);
}

/* Delete a word before the point. */
static void
vty_forward_kill_word (ZVTY *vty)
{
	while (vty->cp != vty->length && vty->buf[vty->cp] == ' ')
		vty_delete_char (vty);
	while (vty->cp != vty->length && vty->buf[vty->cp] != ' ')
		vty_delete_char (vty);
}

/* Delete a word before the point. */
static void
vty_backward_kill_word (ZVTY *vty)
{
	while (vty->cp > 0 && vty->buf[vty->cp - 1] == ' ')
		vty_delete_backward_char (vty);
	while (vty->cp > 0 && vty->buf[vty->cp - 1] != ' ')
		vty_delete_backward_char (vty);
}

/* Transpose chars before or at the point. */
static void
vty_transpose_chars (ZVTY *vty)
{
	char c1, c2;

	/* If length is short or point is near by the beginning of line then
	return. */
	if (vty->length < 2 || vty->cp < 1)
		return;

	/* In case of point is located at the end of the line. */
	if (vty->cp == vty->length)
	{
		c1 = vty->buf[vty->cp - 1];
		c2 = vty->buf[vty->cp - 2];

		vty_backward_char (vty);
		vty_backward_char (vty);
		vty_self_insert_overwrite (vty, c1);
		vty_self_insert_overwrite (vty, c2);
	}
	else
	{
		c1 = vty->buf[vty->cp];
		c2 = vty->buf[vty->cp - 1];

		vty_backward_char (vty);
		vty_self_insert_overwrite (vty, c1);
		vty_self_insert_overwrite (vty, c2);
	}
}

static void
vty_clear_buf (ZVTY *vty)
{
	memset (vty->buf, 0, vty->max);
}

/* ^C stop current input and do not add command line to the history. */
static void
vty_stop_input (ZVTY *vty)
{
	vty->cp = vty->length = 0;
	vty_clear_buf (vty);
	vty_write (vty, "\n",1);

	vty_prompt (vty);

	/* Set history pointer to the latest one. */
	vty->hp = vty->hindex;
}

/* Add current command line to the history buffer. */
static void
vty_hist_add (ZVTY *vty)
{
	int index;

	if (vty->length == 0)
		return;

	index = vty->hindex ? vty->hindex - 1 : VTY_MAXHIST - 1;

	/* Ignore the same string as previous one. */
	if (vty->hist[index])
		if (strcmp (vty->buf, vty->hist[index]) == 0)
		{
			vty->hp = vty->hindex;
			return;
		}

		/* Insert history entry. */
		if (vty->hist[vty->hindex])
			free (vty->hist[vty->hindex]);
		vty->hist[vty->hindex] = strdup (vty->buf);

		/* History index rotation. */
		vty->hindex++;
		if (vty->hindex == VTY_MAXHIST)
			vty->hindex = 0;

		vty->hp = vty->hindex;
}
/* Authentication of vty */
static void
vty_auth (ZVTY *vty, char *buf)
{
	char *passwd = NULL;
	int fail=-1;
	char *crypt (const char *, const char *);
	char* errmsg=NULL;
	switch (vty->node)
	{
	case ZVTY::AUTH_NODE:
		fail = vty->m_stub->vty_auth(vty->name,buf,&errmsg);
		break;
	}

	if (fail>=0)
	{
		vty->fail = 0;
		vty->node = fail;	/* Success ! */
		vty->loginsec=time(NULL);
		if (errmsg&&strlen(errmsg)>0)
			vty_out (vty, "%s\n",errmsg);
	}
	else
	{
		if (errmsg==NULL||strlen(errmsg)==0)
			vty_out(vty,"%s","%% Bad passwords!");
		vty->fail++;
		if (vty->fail >= 3)
		{
			vty_out (vty, "%s\n","\n%% Too many failures!");
			vty->status = ZVTY::VTY_CLOSE;
		}
		else
		{
			vty_out (vty, "%s\n",errmsg?errmsg:"");
			vty->node=ZVTY::NAME_NODE;
		}
	}
	if (errmsg) vty->m_stub->str_free(errmsg);
}
static int
vty_complete_command(ZVTY *vty)
{
	int ret;
	ret = 0;
	switch (vty->node)
	{
	case ZVTY::NAME_NODE:
		break;
	case ZVTY::AUTH_NODE:
		break;
	default:
		return vty->m_stub->vty_complete_command();
	}
	return 0;
}
/* Execute current command line. */
static int
vty_execute (ZVTY *vty)
{
	int ret;

	ret = 0;

	switch (vty->node)
	{
	case ZVTY::NAME_NODE:
		if (vty->name)free(vty->name);
		vty->name=strdup(vty->buf);
		vty->node=ZVTY::AUTH_NODE;
		break;
	case ZVTY::AUTH_NODE:
		vty_auth (vty, vty->buf);
		break;
	default:
		ret = vty->m_stub->vty_execute();
		if (vty->type == ZVTY::VTY_TERM)
			vty_hist_add (vty);
		break;
	}

	/* Clear command line buffer. */
	vty->cp = vty->length = 0;
	vty_clear_buf (vty);

	if (vty->status != ZVTY::VTY_CLOSE )
		vty_prompt (vty);

	return ret;
}

/* Escape character command map. */
static void
vty_escape_map (unsigned char c, struct ZVTY *vty)
{
	switch (c)
	{
	case ('A'):
		vty_previous_line (vty);
		break;
	case ('B'):
		vty_next_line (vty);
		break;
	case ('C'):
		vty_forward_char (vty);
		break;
	case ('D'):
		vty_backward_char (vty);
		break;
	default:
		break;
	}

	/* Go back to normal mode. */
	vty->escape = VTY_NORMAL_ESC;
}

//#define TELNET_OPTION_DEBUG

/* Get telnet window size. */
static int vty_telnet_option (struct ZVTY* vty, unsigned char *buf, int nbytes)
{
#ifdef TELNET_OPTION_DEBUG
	int i;

	for (i = 0; i < nbytes; i++)
	{
		switch (buf[i])
		{
		case IAC:
			vty_out (vty, "IAC ");
			break;
		case WILL:
			vty_out (vty, "WILL ");
			break;
		case WONT:
			vty_out (vty, "WONT ");
			break;
		case DO:
			vty_out (vty, "DO ");
			break;
		case DONT:
			vty_out (vty, "DONT ");
			break;
		case SB:
			vty_out (vty, "SB ");
			break;
		case SE:
			vty_out (vty, "SE ");
			break;
		case TELOPT_ECHO:
			vty_out (vty, "TELOPT_ECHO \n");
			break;
		case TELOPT_SGA:
			vty_out (vty, "TELOPT_SGA \n");
			break;
		case TELOPT_NAWS:
			vty_out (vty, "TELOPT_NAWS \n");
			break;
		default:
			vty_out (vty, "%x ", buf[i]);
			break;
		}
	}
	vty_out (vty, "\n");

#endif /* TELNET_OPTION_DEBUG */

	switch (buf[0])
	{
	case SB:
		vty->sb_len = 0;
		vty->iac_sb_in_progress = 1;
		return 0;
		break;
	case SE:
		{
			if (!vty->iac_sb_in_progress)
				return 0;

			if ((vty->sb_len == 0) || (vty->sb_buf[0] == '\0'))
			{
				vty->iac_sb_in_progress = 0;
				return 0;
			}
			switch (vty->sb_buf[0])
			{
			case TELOPT_NAWS:
				if (vty->sb_len != TELNET_NAWS_SB_LEN)
					printf("RFC 1073 violation detected: telnet NAWS option "
					"should send %d characters, but we received %lu",
					TELNET_NAWS_SB_LEN, (long)vty->sb_len);
				else if (sizeof(vty->sb_buf) < TELNET_NAWS_SB_LEN)
					printf("Bug detected: sizeof(vty->sb_buf) %lu < %d, "
					"too small to handle the telnet NAWS option",
					(long)sizeof(vty->sb_buf), TELNET_NAWS_SB_LEN);
				else
				{
					vty->width = ((vty->sb_buf[1] << 8)|vty->sb_buf[2]);
					vty->height = ((vty->sb_buf[3] << 8)|vty->sb_buf[4]);
#ifdef TELNET_OPTION_DEBUG
					vty_out(vty, "TELNET NAWS window size negotiation completed: "
						"width %d, height %d\n",
						vty->width, vty->height);
#endif
				}
				break;
			}
			vty->iac_sb_in_progress = 0;
			return 0;
			break;
		}
	default:
		break;
	}
	return 1;
}
/* Quit print out to the buffer. */
static void
vty_buffer_reset (ZVTY *vty)
{
	vty->m_stub->vty_outbuf_reset ();
	vty_prompt (vty);
	vty_redraw_line (vty);
}
static int vty_read(ZVTY* vty, unsigned char* buf,int nbytes)
{
	int i;

	for (i = 0; i < nbytes; i++)
	{
		if (buf[i] == IAC)
		{
			if (!vty->iac)
			{
				vty->iac = 1;
				continue;
			}
			else
			{
				vty->iac = 0;
			}
		}

		if (vty->iac_sb_in_progress && !vty->iac)
		{
			if (vty->sb_len < sizeof(vty->sb_buf))
				vty->sb_buf[vty->sb_len] = buf[i];
			vty->sb_len++;
			continue;
		}

		if (vty->iac)
		{
			/* In case of telnet command */
			int ret = 0;
			ret = vty_telnet_option (vty, buf + i, nbytes - i);
			vty->iac = 0;
			i += ret;
			continue;
		}


		if (vty->status == ZVTY::VTY_MORE)
		{
			switch (buf[i])
			{
			case CONTROL('C'):
			case 'q':
			case 'Q':
				vty_buffer_reset (vty);
				break;
#if 0 /* More line does not work for "show ip bgp".  */
			case '\n':
			case '\r':
				vty->status = VTY_MORELINE;
				break;
#endif
			default:
				break;
			}
			continue;
		}

		/* Escape character. */
		if (vty->escape == VTY_ESCAPE)
		{
			vty_escape_map (buf[i], vty);
			continue;
		}

		/* Pre-escape status. */
		if (vty->escape == VTY_PRE_ESCAPE)
		{
			switch (buf[i])
			{
			case '[':
				vty->escape = VTY_ESCAPE;
				break;
			case 'b':
				vty_backward_word (vty);
				vty->escape = VTY_NORMAL_ESC;
				break;
			case 'f':
				vty_forward_word (vty);
				vty->escape = VTY_NORMAL_ESC;
				break;
			case 'd':
				vty_forward_kill_word (vty);
				vty->escape = VTY_NORMAL_ESC;
				break;
			case CONTROL('H'):
			case 0x7f:
				vty_backward_kill_word (vty);
				vty->escape = VTY_NORMAL_ESC;
				break;
			default:
				vty->escape = VTY_NORMAL_ESC;
				break;
			}
			continue;
		}

		switch (buf[i])
		{
		case CONTROL('A'):
			vty_beginning_of_line (vty);
			break;
		case CONTROL('B'):
			vty_backward_char (vty);
			break;
		case CONTROL('C'):
			vty_stop_input (vty);
			break;
		case CONTROL('D'):
			vty_delete_char (vty);
			break;
		case CONTROL('E'):
			vty_end_of_line (vty);
			break;
		case CONTROL('F'):
			vty_forward_char (vty);
			break;
		case CONTROL('H'):
		case 0x7f:
			vty_delete_backward_char (vty);
			break;
		case CONTROL('K'):
			vty_kill_line (vty);
			break;
		case CONTROL('N'):
			vty_next_line (vty);
			break;
		case CONTROL('P'):
			vty_previous_line (vty);
			break;
		case CONTROL('T'):
			vty_transpose_chars (vty);
			break;
		case CONTROL('U'):
			vty_kill_line_from_beginning (vty);
			break;
		case CONTROL('W'):
			vty_backward_kill_word (vty);
			break;
		case CONTROL('Z'):
			//vty_end_config (vty);
			break;
		case '\r':
			//vty_out(vty,"\n");
			//vty_execute (vty);
			break;
		case NUL:
			vty_out(vty,"\n");
			vty_execute (vty);
			break;
		case '\n':
			vty_out(vty,"\n");
			vty_execute (vty);
			break;
		case '\t':
			vty_complete_command(vty);
			break;
		//case '?':
			//if (vty->node == AUTH_NODE || vty->node == AUTH_ENABLE_NODE)
			//	vty_self_insert (vty, buf[i]);
			//else
			//	vty_describe_command (vty);
			//break;
		case '\033':
			if (i + 1 < nbytes && buf[i + 1] == '[')
			{
				vty->escape = VTY_ESCAPE;
				i++;
			}
			else
				vty->escape = VTY_PRE_ESCAPE;
			break;
		default:
			if (buf[i] > 31 && buf[i] < 127)
				vty_self_insert (vty, buf[i]);
			break;
		}
	}

	return 0;
}

void ZVTY::init(ZVTYStubI* stub)
{
	beginsec=time(NULL);
	loginsec=0;
	this->m_stub=stub;
	buf = (char*)malloc(VTY_BUFSIZ);
	name=strdup("?");
	max = VTY_BUFSIZ;
	type = VTY_TERM;

	node = NAME_NODE;
	fail = 0;
	cp = 0;
	vty_clear_buf (this);
	length = 0;
	memset (hist, 0, sizeof (hist));
	hp = 0;
	hindex = 0;
	status = VTY_NORMAL;
	iac = 0;
	iac_sb_in_progress = 0;
	sb_len = 0;
	idle=0;
	escape=0;

	/* Setting up terminal. */
	vty_will_echo (this);
	vty_will_suppress_go_ahead (this);

	vty_dont_linemode (this);
	vty_do_window_size (this);
	/* vty_dont_lflow_ahead (vty); */

	/* Say hello to the world. */
	vty_hello (this);
//	if (! no_password_check)
		vty_out (this, "\nUser Access Verification\n\n");

	vty_prompt(this);
}
void ZVTY::close()
{
	int i;
	/* Free command history. */
	for (i = 0; i < VTY_MAXHIST; i++)
		if (hist[i])
			free(hist[i]);

	if (buf)
		free(buf);
	if (name)
		free(name);
}
int ZVTY::read(unsigned char* buf,int nbytes)
{
	return vty_read(this,buf,nbytes);
}
void ZVTY::insert_word_overwrite(int bac,const char* str)
{
	while (bac--)
		vty_backward_char(this);
	vty_insert_word_overwrite(this,(char*)str);
}
void ZVTY::redraw_line()
{
	vty_redraw_line(this);
}
